package com.vdimitr.happycube.puzzle.matrix;

import java.util.Arrays;

/**
 * This class contains methods for matrix manipulation and comparison.
 * 
 * @author vasileios.dimitriadis
 */
public class MatrixUtils {

	/**
	 * No need to instantiate this class
	 */
	private MatrixUtils() {
	}

	/**
	 * Rotate a NxN matrix counter clockwise in place
	 * 
	 * @param matrix
	 *            a NxN matrix
	 */
	public static void rotateMatrixCounterClockwiseInPlace(int[][] matrix) {
		int width = matrix.length;
		for (int i = 0; i < width / 2; i++) {
			for (int j = i; j < width - i - 1; j++) {
				int temp = matrix[i][j];
				int x = width - i - 1;
				int y = width - j - 1;
				matrix[i][j] = matrix[j][x];
				matrix[j][x] = matrix[x][y];
				matrix[x][y] = matrix[y][i];
				matrix[y][i] = temp;
			}
		}
	}

	/**
	 * Rotate a NxN matrix clockwise in place
	 * 
	 * @param matrix
	 *            a NxN matrix
	 */
	public static void rotateMatrixClockwiseInPlace(int[][] matrix) {
		int width = matrix.length;
		for (int i = 0; i < width / 2; i++) {
			for (int j = i; j < width - i - 1; j++) {
				int temp = matrix[i][j];
				int x = width - i - 1;
				int y = width - j - 1;
				matrix[i][j] = matrix[y][i];
				matrix[y][i] = matrix[x][y];
				matrix[x][y] = matrix[j][x];
				matrix[j][x] = temp;
			}
		}
	}

	/**
	 * Invert a NxN matrix in place based on the middle column
	 * 
	 * @param matrix
	 *            a NxN matrix
	 */
	public static void flipMatrixLeftToRightInPlace(int[][] matrix) {
		int width = matrix.length;
		for (int i = 0; i < width; i++) {
			for (int j = 0; j <= width / 2; j++) {
				int temp = matrix[i][j];
				matrix[i][j] = matrix[i][width - 1 - j];
				matrix[i][width - 1 - j] = temp;
			}
		}
	}

	/**
	 * Create an exact copy of a NxN matrix
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @return a NxN matrix exact copy of the inputMatrix
	 */
	public static int[][] deepCopy(int[][] matrix) {
		int[][] returnMatrix = new int[matrix.length][];
		for (int i = 0; i < matrix.length; i++) {
			returnMatrix[i] = Arrays.copyOf(matrix[i], matrix[i].length);
		}
		return returnMatrix;
	}

	/**
	 * Check if two matrices contain the same elements at the same position
	 * 
	 * @param matrix1
	 *            the first matrix
	 * @param matrix2
	 *            the second matrix
	 * @return true if all corresponding elements are equal
	 */
	public static boolean matricesAreEquals(int[][] matrix1, int[][] matrix2) {
		return Arrays.deepEquals(matrix1, matrix2);
	}

	/**
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @return the first row of the matrix
	 */
	public static int[] getTopRow(int[][] matrix) {
		return getRow(matrix, 0);
	}

	/**
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @return the last row of the matrix
	 */
	public static int[] getBottomRow(int[][] matrix) {
		return getRow(matrix, matrix.length - 1);
	}

	/**
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @param rowIndex
	 *            the row to return
	 * @return the row of the matrix specified by index
	 */
	private static int[] getRow(int[][] matrix, int rowIndex) {
		return matrix[rowIndex];
	}

	/**
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @return the first column of the matrix
	 */
	public static int[] getFirstColumn(int[][] matrix) {
		return getColumn(matrix, 0);
	}

	/**
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @return the last column of the matrix
	 */
	public static int[] getLastColumn(int[][] matrix) {
		return getColumn(matrix, matrix.length - 1);
	}

	/**
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @param columnIndex
	 *            the column to return
	 * @return the column of the matrix specified by index
	 */
	private static int[] getColumn(int[][] matrix, int columnIndex) {
		int[] column = new int[matrix.length];
		for (int i = 0; i < matrix.length; i++) {
			column[i] = matrix[i][columnIndex];
		}
		return column;
	}

	/**
	 * Checks whether a matrix is full of 1s
	 * 
	 * @param matrix
	 *            the matrix to check for completeness
	 * @return true if all elements in the matrix are 1
	 */
	public static boolean isMatrixComplete(int[][] matrix) {
		for (int i = 0; i < matrix.length; i++) {
			for (int j = 0; j < matrix[i].length; j++) {
				int value = matrix[i][j];
				if (value != 1) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Creates a new matrix whose elements derive from the addition of the
	 * elements of the input matrices at the corresponding positions
	 * 
	 * @param matrix1
	 *            first matrix
	 * @param matrix2
	 *            second matrix
	 * @return a new matrix containing the addition of the elements of the input
	 *         matrices
	 */
	public static int[] mergeMatrices(int[] matrix1, int[] matrix2) {
		int[] mergedMatrix = new int[matrix1.length];
		for (int i = 0; i < mergedMatrix.length; i++) {
			mergedMatrix[i] = matrix1[i] + matrix2[i];
		}
		return mergedMatrix;
	}

	/**
	 * Sets the first column of the matrix with the given column matrix
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @param column
	 *            a matrix representing the new column values
	 */
	public static void setFirstColumn(int[][] matrix, int[] column) {
		setColumn(matrix, column, 0);
	}

	/**
	 * Sets the last column of the matrix with the given column matrix
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @param column
	 *            a matrix representing the new column values
	 */
	public static void setLastColumn(int[][] matrix, int[] column) {
		setColumn(matrix, column, matrix.length - 1);
	}

	/**
	 * Sets a column of the matrix with the given column matrix
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @param column
	 *            a matrix representing the new column values
	 * @param columnIndex
	 *            the index of the column to set
	 */
	private static void setColumn(int[][] matrix, int[] column, int columnIndex) {
		for (int i = 0; i < column.length; i++) {
			matrix[i][columnIndex] = column[i];
		}
	}

	/**
	 * Set the first row of the matrix to be the given row
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @param row
	 *            the new row to set
	 */
	public static void setFirstRow(int[][] matrix, int[] row) {
		setRow(matrix, row, 0);
	}

	/**
	 * Set the last row of the matrix to be the given row
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @param row
	 *            the new row to set
	 */
	public static void setLastRow(int[][] matrix, int[] row) {
		setRow(matrix, row, matrix.length - 1);
	}

	/**
	 * Set the row of the matrix specified by the index to be a copy of the
	 * given row
	 * 
	 * @param matrix
	 *            a NxN matrix
	 * @param row
	 *            the new row to set
	 * @param rowIndex
	 *            the index to set the new row to
	 */
	private static void setRow(int[][] matrix, int[] row, int rowIndex) {
		matrix[rowIndex] = Arrays.copyOf(row, row.length);
	}
}
